using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using CI.WebService;

namespace CI.Utils
{
    /// <summary>
    /// Trace Logging Class, will clear all existing listeners and add a file and console listener
    /// Logs info to all attached listners depending on logging level
    /// </summary>
    [Serializable]
    public class TraceLogger
    {
        private const string LogFileBase = "MesopotamiaTrace.log";
        private string _logFile = LogFileBase;
        private string _logFileArchived = "MesopotamiaTraceArchived.log";
        private TextWriterTraceListener _twtl;
        private const int MaxUploadLogSize = 512000;
        private const int MaxFileSize = 20000000;
        private bool _clearedLog;
        private string _logPath;
        private FileInfo _fi;
        private FileInfo _fiArchive;
        private readonly object _syncObject = new object();

        private void SetFileInfo()
        {
            _logPath = ResourceFolders.UsersResourceFolder();
            _logFile = Path.Combine(_logPath, "MesopotamiaTrace.log");
            _logFileArchived = Path.Combine(_logPath, "MesopotamiaTraceArchived.log");
            _fi = new FileInfo(_logFile);
            _fiArchive = new FileInfo(_logFileArchived);
        }

        /// <summary>
        /// Log informational message        
        /// </summary>
        /// <param name="logMessage">string message to be logged</param>
        [Conditional("DEBUG")]
        public void LogDebug(string logMessage)
        {
            LogMessage(logMessage);
        }

        private void LogMessage(string logMessage)
        {
            // make sure the log file has been setup
            if (Monitor.TryEnter(_syncObject))
            {
                try
                {
                    // if not init it
                    if (_twtl == null)
                        Init();

                    if (_fi != null)
                    {
                        // get the file info
                        _fi.Refresh();
                        //  make sure it exists
                        if (_fi.Exists)
                        {
                            // if it is larger then the maximum size
                            if (_fi.Length > MaxFileSize)
                            {
                                // close log file
                                Close();
                                // init log file
                                Init();
                            }
                        }
                    }
                    try
                    {
                        // write the log message
                        Trace.WriteLine(string.Format("{0}:{1}:{2} ", DateTime.Now.TimeOfDay, Thread.CurrentThread.ManagedThreadId, logMessage));
                    }
                    catch (ObjectDisposedException oEx)
                    {
                        // clear the trace listeners
                        Trace.Listeners.Clear();

                        // dump info to console
                        Console.WriteLine("Error logging exception : " + oEx.Message);
                        Console.WriteLine("Error trying to log : " + logMessage);
                    }
                    catch (IOException ioEx)
                    {
                        // todo : research, im seeing console.writeline throw System.IO.IOException,  "Not enough storage is available to process this command"
                        // log the error to the file and continue
                        _twtl.WriteLine("Error logging exception: " + ioEx.Message);
                        _twtl.WriteLine("Error trying to log : " + logMessage);
                    }

                }
                finally
                {
                    Monitor.Exit(_syncObject);
                }
            }
        }

        /// <summary>
        /// Log a warning message
        /// </summary>
        /// <param name="logMessage">string message to be logged</param>
        public void LogWarn(string logMessage)
        {
            // write the log message
            LogMessage("Warning : " + logMessage);
        }

        /// <summary>
        /// Log an Exception and message
        /// </summary>
        /// <param name="logMessage">String message or area of exception</param>
        /// <param name="exception">Exception thrown</param>
        public void LogException(string logMessage, Exception exception)
        {
            // log the message
            LogMessage("Exception : " + exception.Message + "\r\n" + exception.StackTrace + "\r\n" + logMessage);
        }

        /// <summary>
        /// get rid of the guid named trace files that can build up
        /// </summary>
        public void PurgeGUIDLongFilenameLogFiles()
        {
            Console.WriteLine("Not Logged - Start - Purging temp log files, this could take a while");
            var di = new DirectoryInfo(ResourceFolders.UsersResourceFolder());
            FileInfo[] rgFiles = di.GetFiles("*.log");
            foreach (FileInfo fi in rgFiles)
            {
                // if it is a really long filename eg guid style one
                if ((fi.Name.Length > LogFileBase.Length) && (fi.Name.Contains(LogFileBase)))
                {
                    Console.WriteLine("Not Logged - Deleting :" + fi.Name);
                    try
                    {
                        File.Delete(fi.FullName);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Exception :" + ex.Message);
                    }
                }
            }
            Console.WriteLine("Not Logged - Finish");
        }


        /// <summary>
        /// Init the trace loggers
        /// </summary>
        private void Init()
        {

            // if running in website mode don't init
            if (Globals.LaunchedFrom == LaunchType.WCFService)
                return;

            SetFileInfo();

            // only delete log file on startup
            if (!_clearedLog)
            {
                // delete old log file
                try
                {
                    // startup background processing thread
                    var logPurge = new Thread(() =>
                                                  {
                                                      Console.WriteLine("Running Purge logs on background thread");
                                                      PurgeGUIDLongFilenameLogFiles();
                                                  });

                    // purge long files
                    logPurge.Start();

                    // if file exists - archive
                    // get the file info
                    _fi.Refresh();

                    //  make sure it exists
                    if (_fi.Exists)
                    {
                        // if archive file exist delte it
                        _fiArchive.Refresh();
                        if (_fiArchive.Exists)
                            File.Delete(_logFileArchived);

                        // rename it to the archive file
                        File.Move(_logFile, _logFileArchived);
                    }
                    _clearedLog = true;
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                }
            }

            // add a textwriter trace listener
            _twtl = new TextWriterTraceListener(_logFile);
            _twtl.WriteLine("Log init");

            // add file logger
            Trace.Listeners.Add(_twtl);

            // console listener
            bool consoleListener = false;
            bool defaultTraceListner = false;
            // add console logger if we only have a normal logfile
            foreach (TraceListener listener in Trace.Listeners)
            {
                if (listener.GetType().Equals(typeof(ConsoleTraceListener)))
                    consoleListener = true;
                if (listener.GetType().Equals(typeof(DefaultTraceListener)))
                    defaultTraceListner = true;
            }

            // if no console listener
            if (!consoleListener)
                Trace.Listeners.Add(new ConsoleTraceListener());

            // remove the visual studio default logger as it is very slow
            //if (defaultTraceListner)
            //    Trace.Listeners.Remove("Default");


            // turn on autoflush
            Trace.AutoFlush = true;


        }

        /// <summary>
        /// Close the trace logger
        /// </summary>
        public void Close()
        {
            if (_twtl == null)
                return;

            try
            {
                _twtl.WriteLine("Closing Logs");
                Trace.Listeners.Remove(_twtl);
                // flush and close
                _twtl.Flush();
                _twtl.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            // setting to null will cause a reinit when they start loggin
            _twtl = null;
            // set it not being cleared yet
            _clearedLog = false;
        }

        /// <summary>
        /// Upload Logs
        /// </summary>
        public void UploadLogs(string fileName)
        {
            // connect to host
            var client = new LogClient();
#if (DEBUG&&GRIDLOCAL)
            // if debug use the local endpoint for debugging
            client.Endpoint.Address = new System.ServiceModel.EndpointAddress("http://localhost/MesopotamiaDCProxy/inbound.svc");
#endif
            // upload the logs
            try
            {
                
                DumpStats();

                if (Monitor.TryEnter(_syncObject, Globals.CriticalAcquireLockTimeout))
                {
                    try
                    {
                        // flush and close
                        FlushAndClose();

                        // get the file info
                        var fi = new FileInfo(_logFile);
                        //  make sure it exists
                        if (fi.Exists)
                        {

                            byte[] bytes = LoadFile();

                            try
                            {
                                // call the function
                                client.UploadLogs(bytes, fileName);
                            }
                            catch (System.ServiceModel.EndpointNotFoundException ex)
                            {
                                LogException("Error uploading log file - service not found", ex);
                            }
                            catch (Exception ex)
                            {
                                LogException("Error uploading log file", ex);
                            }
                        }
                    }
                    finally
                    {
                        Monitor.Exit(_syncObject);
                    }
                }
                else
                {
                    throw new Exception("Unable to acquire lock on _logFile");
                }

                LogDebug("Upload Complete");
            }
            catch (Exception ex)
            {
                LogException("Error uploading the logs", ex);
            }
            finally
            {
                client.Close();
            }
        }

        private byte[] LoadFile()
        {
            var fileReader = new BinaryReader(File.Open(_logFile, FileMode.Open));
            var fileReadSize = MaxUploadLogSize;

            // if we are longer then the max upload size seek from the end to only get the max upload size we want
            if (fileReader.BaseStream.Length > MaxUploadLogSize)
            {
                // seek relative
                fileReader.BaseStream.Seek(-MaxUploadLogSize, SeekOrigin.End);
            }
            else
            {
                // if we are smaller then the max upload size then only read the lenght of the file
                fileReadSize = (int)fileReader.BaseStream.Length;
            }

            // create the array to the read size available
            var bytes = new byte[fileReadSize];

            // read the info out
            fileReader.Read(bytes, 0, fileReadSize);

            // close the reader
            fileReader.Close();
            return bytes;
        }

        private void FlushAndClose()
        {
            _twtl.Flush();
            // remove the listerner
            Trace.Listeners.Remove(_twtl);
            _twtl.Close();
            // setting to null will cause a reinit when they start logging
            _twtl = null;
        }

        private void DumpStats()
        {
            SimEnv simEnv = SimEnv.Instance;
            LogMessage(string.Format("System uptime : {0} on host {1} at {2}", DateTime.Now.Subtract(simEnv.StartTime), Environment.MachineName, DateTime.Now));
            if (simEnv.HostEnv != null)
                LogMessage(string.Format("Hosting Environment {0} version {1}", simEnv.HostEnv.GetType(), simEnv.HostEnv.VersionInformation));
            LogMessage(string.Format("Start mode {0}", Globals.LaunchedFrom));
            LogMessage("Preparing upload of logs for diagnostics");
        }
    }
}